1 module kprop.api.digitalocean.digitalocean;
2 import std.stdio;
3 import std.json;
4 import std.net.curl;
5 import std.exception:Exception,enforce,assumeUnique;
6 import std.conv:to;
7 import std.algorithm:countUntil,map,each;
8 import std.traits:EnumMembers;
9 import std.array:array,appender;
10 import std.format:format;
11 import std.variant:Algebraic;
12 import kprop.api.digitalocean.auth; // replace by your own key
13 import kprop.helper.prettyjson;
14 
15 /**
16     Implemented in the D Programming Language 2015 by Laeeth Isharc and Kaleidic Associates
17     Boost Licensed
18     Use at your own risk - this is not tested at all and if you end up deleting all your
19     instances and creating 10,000 pricey new ones then it will not be my fault
20 */
21 
22 static this()
23 {
24     OceanRegions=[EnumMembers!OceanRegion].map!(a=>a.toString).array.assumeUnique;
25     DropletActions=[EnumMembers!DropletAction].map!(a=>a.toString).array.assumeUnique;
26     OceanImages=
27     [
28         OceanGlobalImage(false,"CoreOS-766.4.0-(beta)",13578467,OceanDistro.CoreOS,""),
29         OceanGlobalImage(false,"CentOS-5.10-x64",6372321,OceanDistro.CentOS,""),
30         OceanGlobalImage(false,"5.10 x32",6372425, OceanDistro.CentOS),
31         OceanGlobalImage(false,"6.0 x64",6372581, OceanDistro.Debian),
32         OceanGlobalImage(false,"6.0 x32",6372662, OceanDistro.Debian),
33         OceanGlobalImage(false,"21 x64",9640922, OceanDistro.Fedora),
34         OceanGlobalImage(false,"10.1",10144573, OceanDistro.FreeBSD),
35         OceanGlobalImage(false,"12.04.5 x64",10321756, OceanDistro.Ubuntu),
36         OceanGlobalImage(false,"12.04.5 x32",10321777, OceanDistro.Ubuntu),
37         OceanGlobalImage(false,"7.0 x64",10322059, OceanDistro.Debian),
38         OceanGlobalImage(false,"7.0 x32",10322378, OceanDistro.Debian),
39         OceanGlobalImage(false,"7 x64",10322623, OceanDistro.CentOS),
40         OceanGlobalImage(false,"22 x64",12065782, OceanDistro.Fedora),
41         OceanGlobalImage(false,"15.04 x64",12658446, OceanDistro.Ubuntu),
42         OceanGlobalImage(false,"15.04 x32",12660649, OceanDistro.Ubuntu),
43         OceanGlobalImage(false,"8.1 x64",12778278, OceanDistro.Debian),
44         OceanGlobalImage(false,"8.1 x32",12778337, OceanDistro.Debian),
45         OceanGlobalImage(false,"14.04 x64",13089493, OceanDistro.Ubuntu),
46         OceanGlobalImage(false,"14.04 x32",13089823, OceanDistro.Ubuntu),
47         OceanGlobalImage(false,"6.7 x64",13090046, OceanDistro.CentOS),
48         OceanGlobalImage(false,"6.7 x32",13090097, OceanDistro.CentOS),
49         OceanGlobalImage(false,"10.2",13321858, OceanDistro.FreeBSD),
50         OceanGlobalImage(false,"815.0.0 (alpha)",13683512, OceanDistro.CoreOS),
51         OceanGlobalImage(false,"766.4.0 (stable)",13750582, OceanDistro.CoreOS),
52         OceanGlobalImage(false,"FreeBSD AMP on 10.1",10163059, OceanDistro.FreeBSD),
53         OceanGlobalImage(true,"Drone on 14.04",11774848, OceanDistro.Ubuntu,"Drone"),
54         OceanGlobalImage(true,"Cassandra on 14.04",12540744, OceanDistro.Ubuntu,"Cassandra"),
55         OceanGlobalImage(true,"ELK Logging Stack on 14.04",12542038, OceanDistro.Ubuntu,"ELK Logging"),
56         OceanGlobalImage(true,"Django on 14.04",12740667, OceanDistro.Ubuntu,"Django"),
57         OceanGlobalImage(true,"Mumble Server (murmur) on 14.04",12914152, OceanDistro.Ubuntu,"murmur"),
58         OceanGlobalImage(true,"Joomla! 3.4.3 on 14.04",13014869, OceanDistro.Ubuntu,"Joomla"),
59         OceanGlobalImage(true,"Magento-1.9.2.1 CE on 14.04",13115155, OceanDistro.Ubuntu,"Magento"),
60         OceanGlobalImage(true,"MongoDB 3.0.5 on 14.04",13115659, OceanDistro.Ubuntu,"MongoDB"),
61         OceanGlobalImage(true,"LEMP on 14.04",13138234, OceanDistro.Ubuntu,"LEMP"),
62         OceanGlobalImage(true,"LAMP on 14.04",13138235, OceanDistro.Ubuntu,"LAMP"),
63         OceanGlobalImage(true,"MediaWiki on 14.04",13185409, OceanDistro.Ubuntu,"MediaWiki"),
64         OceanGlobalImage(true,"WordPress on 14.04",13229890, OceanDistro.Ubuntu,"WordPress"),
65         OceanGlobalImage(true,"Ruby on Rails on 14.04 (Postgres, Nginx, Unicorn)",13400199, OceanDistro.Ubuntu,"Ruby on Rails"),
66         OceanGlobalImage(true,"MEAN on 14.04",13413549, OceanDistro.Ubuntu,"MEAN"),
67         OceanGlobalImage(true,"Drupal 7.39 on 14.04",13414327, OceanDistro.Ubuntu,"Drupal"),
68         OceanGlobalImage(true,"Docker 1.8.2 on 14.04",13495049, OceanDistro.Ubuntu,"Docker"),
69         OceanGlobalImage(true,"node v4.1.0 on 14.04",13586846, OceanDistro.Ubuntu,"nodejs"),
70         OceanGlobalImage(true,"Redis 3.0.4 on 14.04",13601457, OceanDistro.Ubuntu,"Redis"),
71         OceanGlobalImage(true,"ownCloud 8.1.3 on 14.04",13603669, OceanDistro.Ubuntu,"ownCloud"),
72         OceanGlobalImage(true,"GitLab 8.0.1 CE on 14.04",13670476, OceanDistro.Ubuntu,"GitLab"),
73         OceanGlobalImage(true,"Ghost 0.7.1 on 14.04",13750431, OceanDistro.Ubuntu,"Ghost"),
74         OceanGlobalImage(true,"Dokku v0.4.1 on 14.04",13750783, OceanDistro.Ubuntu,"Dokku"),
75         OceanGlobalImage(true,"Discourse on 14.04",13846109, OceanDistro.Ubuntu,"Discourse"),
76         OceanGlobalImage(true,"PHPMyAdmin on 14.04",11730661, OceanDistro.Ubuntu,"PHPMyAdmin"),
77         OceanGlobalImage(true,"Redmine on 14.04",12438838, OceanDistro.Ubuntu,"Redmine"),
78     ];
79 }
80 
81 string joinUrl(string url, string endpoint)
82 {
83     enforce(url.length>0, "broken url");
84     if (url[$-1]=='/')
85         url=url[0..$-1];
86     return url~"/"~endpoint;
87 }
88 /**
89     auto __str__(self):
90         return b"<{:s} at {:#x}>".format(type(self).__name__, id(self))
91 
92     auto __unicode__(self):
93         return "<{:s} at {:#x}>".format(type(self).__name__, id(self))
94 */
95 
96 struct OceanAPI
97 {
98     string endpoint = "https://api.digitalocean.com/v2/";
99     string token;
100 
101     this(string token)
102     {
103         this.token=token;
104     }
105     this(string endpoint, string token)
106     {
107         this.endpoint=endpoint;
108         this.token=token;
109     }
110 }
111 
112 JSONValue request(OceanAPI api, string url, HTTP.Method method=HTTP.Method.get, JSONValue params=JSONValue(null))
113 {
114     enforce(api.token.length>0,"no token provided");
115     url=api.endpoint.joinUrl(url);
116     auto client=HTTP(url);
117     client.addRequestHeader("Authorization", "Bearer "~api.token);
118     auto response=appender!(ubyte[]);
119     client.method=method;
120     switch(method) with(HTTP.Method)
121     {
122         case del:
123             client.setPostData(cast(void[])params.toString,"application/x-www-form-urlencoded");
124             break;
125         case get,head:
126             client.setPostData(cast(void[])params.toString,"application/json");
127             break;
128         default:
129             client.setPostData(cast(void[])params.toString,"application/json");
130             break;
131     }
132     client.onReceive = (ubyte[] data)
133     {
134         response.put(data);
135         return data.length;
136     };
137     client.perform();                 // rely on curl to throw exceptions on 204, >=500
138     return parseJSON(cast(string)response.data);
139 }
140 
141 
142 // List all Actions
143 auto listActions(OceanAPI api)
144 {
145     return api.request("actions",HTTP.Method.get);
146 }
147 
148 // retrieve existing Action
149 auto retrieveAction(OceanAPI api, string id)
150 {
151     return api.request("actions/"~id, HTTP.Method.get);
152 }
153 
154 auto allNeighbours(OceanAPI api)
155 {
156     return api.request("reports/droplet_neighbors",HTTP.Method.get);
157 }
158 
159 auto listUpgrades(OceanAPI api)
160 {
161     return api.request("droplet_upgrades",HTTP.Method.get);    
162 }
163 
164 // List all Domains (managed through Ocean DNS interface)
165 auto listDomains(OceanAPI api)
166 {
167     return api.request("domains", HTTP.Method.get);
168 }
169 
170 struct OceanDomain
171 {
172     OceanAPI api;
173     string id;
174     alias id this;
175     this(OceanAPI api, string id)
176     {
177         this.api=api;
178         this.id=id;
179     }
180     // Create new Domain
181     static auto create(OceanAPI api, string name, string ip)
182     {
183         JSONValue params;
184         params["name"]=name;
185         params["ip_address"]=ip;
186         return api.request("domains", HTTP.Method.post, params);
187     }
188     auto request(string url, HTTP.Method method=HTTP.Method.get, JSONValue params=JSONValue(null))
189     {
190         return api.request(url,method,params);
191     }
192 }
193 
194 // Retrieve an existing Domain
195 auto get(OceanDomain domain)
196 {
197     return domain.request("domains/"~domain.id, HTTP.Method.get);
198 }
199 
200  // Delete a Domain
201 auto del(OceanDomain domain)
202 {
203     return domain.request("domains/"~domain.id, HTTP.Method.del);
204 }
205 
206 //  List all Domain Records
207 auto listDomainRecords(OceanDomain domain)
208 {
209     return domain.request(format("domains/%s/records",domain.id), HTTP.Method.get);
210 }
211 
212 //  Create a new Domain Record
213 auto createRecord(OceanDomain domain, string rtype=null, string name=null, string data=null,
214                          string priority=null, string port=null, string weight=null)
215 {
216     JSONValue params;
217     params["type"]=rtype;
218     if(name.length>0)
219         params["name"]=name;
220     if(data.length>0)
221         params["data"]=data;
222     if(priority.length>0)
223         params["priority"]=priority;
224     if(port.length>0)
225         params["port"]=port;
226     if(weight.length>0)
227         params["weight"]=weight;
228     return domain.request(
229         format("domains/%s/records",domain.id), HTTP.Method.post, params);
230 }
231 
232 //  Retrieve an existing Domain Record
233 auto getRecord(OceanDomain domain, string recordId)
234 {
235     return domain.request(
236         format("domains/%s/records/%s",domain.id,recordId), HTTP.Method.get);
237 }
238 
239 //  Delete a Domain Record
240 auto delRecord(OceanDomain domain, string recordId)
241 {
242     return domain.request(
243         format("domains/%s/records/%s",domain.id,recordId), HTTP.Method.del);
244 }
245 
246 //  Update a Domain Record
247 auto updateRecord(OceanDomain domain, string recordId,string name)
248 {
249     JSONValue params;
250     params["name"] = name;
251     return domain.request(format("domains/%s/records/%s",domain.id, recordId), HTTP.Method.put, params);
252 }
253 
254 
255 // list all droplets
256 auto listDroplets(OceanAPI api)
257 {
258     return api.request("droplets",HTTP.Method.get);
259 }
260 
261 enum OceanRegion
262 {
263     ams2,
264     ams3,
265     fra1,
266     lon1,
267     nyc1,
268     nyc2,
269     nyc3,
270     sfo1,
271     sgp1,
272     tor1,
273 }
274 
275 
276 immutable string[] OceanRegions;
277 OceanRegion oceanRegion(string region)
278 {
279     OceanRegion ret;
280     auto i=OceanRegions.countUntil(region);
281     enforce(i>=0, new Exception("unknown droplet region: "~region));
282     return cast(OceanRegion)i;
283 }
284 
285 string toString(OceanRegion region)
286 {
287     final switch(region) with(OceanRegion)
288     {
289         case ams2:
290             return "Amsterdam 2";
291         case ams3:
292             return "Amsterdam 3";
293         case fra1:
294             return "Frankfurt 1";
295         case lon1:
296             return "London 1";
297         case nyc1:
298             return "New York 1";
299         case nyc2:
300             return "New York 2";
301         case nyc3:
302             return "New York 3";
303         case sfo1:
304             return "San Francisco 1";
305         case sgp1:
306             return "Singapore 1";
307         case tor1:
308             return "Toronto 1";
309     }
310     assert(0);
311 }
312 
313 enum OceanDistro
314 {
315     CoreOS,
316     Debian,
317     Fedora,
318     CentOS,
319     FreeBSD,
320     Ubuntu,
321 }
322 alias OceanImageId=Algebraic!(int,string);
323 struct OceanGlobalImage
324 {
325     bool isApplication=false;
326     string slug;
327     int id;
328     OceanDistro distro;
329     string application;
330 }
331 OceanGlobalImage[] OceanImages;
332 
333 
334 struct Droplet
335 {
336     OceanAPI api;
337     int id;
338 
339     this(OceanAPI api, int id)
340     {
341         this.api=api;
342         this.id=id;
343     }
344 
345     string toString()
346     {
347         return id.to!string;
348     }
349     auto request(string uri, HTTP.Method method=HTTP.Method.get, JSONValue params=JSONValue(null))
350     {
351         return api.request(uri,method,params);
352     }
353 
354     //  Create a new Droplet
355     static auto create(OceanAPI api,string name, OceanRegion region, string size, OceanImageId image, string[] sshKeys, string backups=null,
356                string ipv6=null, string privateNetworking=null, string userData=null)
357     {
358         JSONValue params;
359         params["name"]=name;
360         params["region"]=region.to!string;
361         params["size"]=size;
362         if(image.type==typeid(string))
363             params["image"]=image.get!string;
364         else
365             params["image"]=image.get!int;
366         if (sshKeys.length>0)
367             params["ssh_keys"]=sshKeys;
368         if(backups.length>0)
369             params["backups"]=backups.to!bool;
370         if (ipv6.length>0)
371             params["ipv6"]=ipv6.to!bool;
372         if (privateNetworking.length>0)
373             params["private_networking"]=privateNetworking.to!bool;
374         if (userData.length>0)
375             params["user_data"]=userData;
376         return api.request("droplets", HTTP.Method.post, params);
377     }
378 }
379 
380 //  Makes an action
381 JSONValue action(Droplet droplet, DropletAction actionType,JSONValue params=JSONValue(null))
382 {
383     params["type"]=actionType.toString;
384     return droplet.request(format("droplets/%s/actions",droplet.id), HTTP.Method.post, params);
385 }
386 
387 enum DropletAction
388 {
389     reboot,
390     powerCycle,
391     shutdown,
392     powerOff,
393     powerOn,
394     passwordReset,
395     resize,
396     restore,
397     rebuild,
398     rename,
399     changeKernel,
400     enableIPv6,
401     disableBackups,
402     enablePrivateNetworking,
403     snapshot,
404     upgrade,
405 }
406 
407 string toString(DropletAction action)
408 {
409     final switch(action) with(DropletAction)
410     {
411         case reboot:
412             return "reboot";
413         case powerCycle:
414             return "power_cycle";
415         case shutdown:
416             return "shutdown";
417         case powerOff:
418             return "power_off";
419         case powerOn:
420             return "power_on";
421         case passwordReset:
422             return "password_reset";
423         case resize:
424             return "resize";
425         case restore:
426             return "restore";
427         case rebuild:
428             return "rebuild";
429         case rename:
430             return "rename";
431         case changeKernel:
432             return "change_kernel";
433         case enableIPv6:
434             return "enable_ipv6";
435         case disableBackups:
436             return "disable_backups";
437         case enablePrivateNetworking:
438             return "enable_private_networking";
439         case snapshot:
440             return "snapshot";
441         case upgrade:
442             return "upgrade";
443     }
444 }
445 
446 
447 immutable string[] DropletActions;
448 DropletAction dropletAction(string action)
449 {
450     auto i=DropletActions.countUntil(action);
451     enforce(i>=0,new Exception("unknown droplet action: "~action));
452     return cast(DropletAction)i;
453 }
454 
455 struct OceanResult(T)
456 {
457     bool found;
458     T result;
459 }
460 // find droplet ID from anme
461 OceanResult!Droplet findDroplet(OceanAPI ocean, string name)
462 {
463     auto ret=ocean.Droplet(-1);
464     auto dropletResults=ocean.listDroplets;
465     auto droplets="droplets" in dropletResults;
466     enforce(droplets !is null, new Exception("bad response from Digital Ocean: "~dropletResults.prettyPrint));
467     enforce((*droplets).type==JSON_TYPE.ARRAY, new Exception
468         ("bad response from Digital Ocean: "~dropletResults.prettyPrint));
469     (*droplets).array.each!(a=>enforce(("name" in a.object) && a.object["name"].type==JSON_TYPE.STRING));
470     auto i=(*droplets).array.map!(a=>a.object["name"].str).array.countUntil(name);
471     if (i==-1)
472     {
473         return OceanResult!Droplet(false,ret);
474     }
475     //auto p=("id" in ((*droplets).array[i]));
476     //enforce(p !is null, new Exception
477       //  ("findDroplet cannot find id in results - malformed JSON?\n"~dropletResults.prettyPrint));
478     return OceanResult!Droplet(true,ocean.Droplet((*droplets).array[i].object["id"].integer.to!int));
479 }
480 
481 //  List all available Kernels for a Droplet
482 auto kernels(Droplet droplet)
483 {
484     return droplet.request(format("droplets/%s/kernels",droplet.id), HTTP.Method.get);
485 }
486 
487 
488 //  Retrieve snapshots for a Droplet
489 auto snapshots(Droplet droplet)
490 {
491     return droplet.request(format("droplets/%s/snapshots",droplet.id), HTTP.Method.get);
492 }
493 
494 //  Retrieve backups for a Droplet
495 auto backups(Droplet droplet)
496 {
497   return droplet.request(format("droplets/%s/backups",droplet.id), HTTP.Method.get);
498 }
499 
500 //  Retrieve actions for a Droplet
501 auto actions(Droplet droplet)
502 {
503     return droplet.request(format("droplets/%s/actions",droplet.id), HTTP.Method.get);
504 }
505 
506 //  Retrieve an existing Droplet by id
507 auto retrieve(Droplet droplet)
508 {
509     return droplet.request("droplets/"~droplet.id.to!string, HTTP.Method.get);
510 }
511 
512 //  Delete a Droplet
513 auto del(Droplet droplet)
514 {
515     return droplet.request("droplets/"~droplet.id.to!string, HTTP.Method.del);
516 }
517 
518 auto neighbours(Droplet droplet)
519 {
520     return droplet.request(format("droplets/%s/neighbors",droplet.id),HTTP.Method.get);
521 }
522 
523 //  Reboot a Droplet
524 auto reboot(Droplet droplet)
525 {
526     return droplet.action(DropletAction.reboot);
527 }
528 
529 //  Power Cycle a Droplet
530 auto powerCycle(Droplet droplet)
531 {
532     return droplet.action(DropletAction.powerCycle);
533 }
534 
535 //  Shutdown a Droplet
536 auto shutdown(Droplet droplet)
537 {
538     return droplet.action(DropletAction.shutdown);
539 }
540 
541 //  Power Off a Droplet
542 auto powerOff(Droplet droplet)
543 {
544     return droplet.action(DropletAction.powerOff);
545 }
546 
547 //  Power On a Droplet
548 auto powerOn(Droplet droplet)
549 {
550     return droplet.action(DropletAction.powerOn);
551 }
552 
553 //  Password Reset a Droplet
554 auto passwordReset(Droplet droplet)
555 {
556     return droplet.action(DropletAction.passwordReset);
557 }
558 
559 //  Resize a Droplet
560 auto resize(Droplet droplet, string size)
561 {
562     JSONValue params;
563     params["size"]=size;
564     return droplet.action(DropletAction.resize, params);
565 }
566 
567 //  Restore a Droplet
568 auto restore(Droplet droplet, string image)
569 {
570     JSONValue params;
571     params["image"]=image;
572     return droplet.action(DropletAction.restore, params);
573 }
574 
575 //  Rebuild a Droplet
576 auto rebuild(Droplet droplet, string image)
577 {
578     JSONValue params;
579     params["image"]=image;
580     return droplet.action(DropletAction.rebuild, params);
581 }
582 
583 //  Rename a Droplet
584 auto rename(OceanAPI api, Droplet droplet, string name)
585 {
586     JSONValue params;
587     params["name"]=name;
588     return droplet.action(DropletAction.rename,params);
589 }
590 
591 //  Change the Kernel
592 auto changeKernel(Droplet droplet, string kernel)
593 {
594     JSONValue params;
595     params["kernel"]=kernel;
596     return droplet.action(DropletAction.changeKernel, params);
597 }
598 
599 //  Enable IPv6
600 auto enableIPv6(Droplet droplet)
601 {
602     return droplet.action(DropletAction.enableIPv6);
603 }
604 
605 //  Disable Backups
606 auto disableBackups(Droplet droplet)
607 {
608     return droplet.action(DropletAction.disableBackups);
609 }
610 
611 //  Enable Private Networking
612 auto enablePrivateNetworking(Droplet droplet)
613 {
614     return droplet.action(DropletAction.enablePrivateNetworking);
615 }
616 
617 //  Snapshot
618 auto doSnapshot(Droplet droplet, string name=null)
619 {
620     JSONValue params;
621     if (name.length>0)
622         params["name"]=name;
623     return droplet.action(DropletAction.snapshot, params);
624 }
625 
626 //  Retrieve a Droplet Action
627 auto retrieveAction(Droplet droplet, string actionId)
628 {
629     return droplet.request(
630         format("droplets/%s/actions/%s",droplet.id, actionId), HTTP.Method.get);
631 }
632 
633 auto upgrade(Droplet droplet)
634 {
635     JSONValue params;
636     params["upgrade"]=true;
637     droplet.action(DropletAction.upgrade,params);
638 }
639 struct OceanImage
640 {
641     OceanAPI api;
642     string id;
643     alias id this;
644     this(OceanAPI api, string id)
645     {
646         this.api=api;
647         this.id=id;
648     }
649     auto request(string uri, HTTP.Method method=HTTP.Method.get, JSONValue params=JSONValue(null))
650     {
651         return api.request(uri,method,params);
652     }
653 }
654 // List all images
655 auto listImages(OceanAPI api)
656 {
657     return api.request("images", HTTP.Method.get);
658 }
659 
660 // Retrieve an existing Image by id or slug
661 auto get(OceanImage image)
662 {
663     return image.request("images/"~image.id, HTTP.Method.get);
664 }
665 
666 //  Delete an Image
667 auto del(OceanImage image)
668 {
669     return image.request("images/"~image.id, HTTP.Method.del);
670 }
671 
672 //  Update an Image
673 auto update(OceanImage image, string name)
674 {
675     JSONValue params;
676     params["name"]=name;
677     return image.request("images/"~image.id, HTTP.Method.put, params);
678 }
679 
680 //  Transfer an Image
681 auto transfer(OceanImage image, OceanRegion region)
682 {
683     JSONValue params;
684     params["type"]="transfer";
685     params["region"]=region.to!string;
686     return image.request(format("images/%s/actions",image.id), HTTP.Method.post,params);
687 }
688 
689  // Retrieve an existing Image Action
690 
691 auto getImageAction(OceanImage image, string actionId)
692 {
693     return image.request(format("images/%s/actions/%s",image.id,actionId), HTTP.Method.get);
694 }
695 
696 struct OceanKey
697 {
698     OceanAPI api;
699     string value;
700 
701     this(OceanAPI api,string key)
702     {
703         this.api=api;
704         this.value=key;
705     }
706     auto request(string uri, HTTP.Method method=HTTP.Method.get, JSONValue params=JSONValue(null))
707     {
708         return api.request(uri,method,params);
709     }
710 
711     // Create a new Key
712     static auto create(OceanAPI api, string name, string publicKey)
713     {
714         JSONValue params;
715         params["name"]=name;
716         params["public_key"]=publicKey;
717         return api.request("account/keys", HTTP.Method.post, params);
718     }
719 }
720 
721 // list all keys
722 auto listKeys(OceanAPI api)
723 {
724     return api.request("account/keys", HTTP.Method.get);
725 }
726 
727 
728 // Retrieve an existing Key by Id or Fingerprint
729 
730 auto retrieve(OceanKey key)
731 {
732     return key.request("account/keys/"~key.value, HTTP.Method.get);
733 }
734 
735 //  Update an existing Key by Id or Fingerprint
736 auto updateName(OceanKey key, string name)
737 {
738     JSONValue params;
739     params["name"]=name;
740     return key.request("account/keys/"~key.value, HTTP.Method.put, params);
741 }
742 
743 //  Destroy an existing Key by Id or Fingerprint
744 auto del(OceanKey key)
745 {
746     return key.request("account/keys/"~key.value, HTTP.Method.del);
747 }
748 
749 
750 // list all regions
751 auto listRegions(OceanAPI api)
752 {
753    return api.request("regions", HTTP.Method.get);
754 }
755 
756 // list all sizes
757 auto listSizes(OceanAPI api)
758 {
759     return api.request("sizes", HTTP.Method.get);
760 }